#include "module_frame.hh"
#include "framework.hh"
#include "entity.hh"
#include "module_frame_http.hh"
#include "entity_search_cache.hh"
#include "../protocol/protocol_header.hh"
#include "../../corelib/redis/redis.hh"
#include "../../corelib/mysql/mysql.hh"
#include "rpc_base.hh"

ModuleFrame::ModuleFrame()
    : engine_(nullptr), framework_(nullptr),
          sendBuffer_(new char[kratos::NETWORK_IOLOOP_RECV_BUFFER_MAX_SIZE]), moduleType_(ModuleType::NONE),
              stopped_(false), started_(false), installed_(false) {
}

ModuleFrame::~ModuleFrame() {
}

bool ModuleFrame::install(Engine * engine) {
    if (installed_) {
        return true;
    }
    engine_ = engine;
    auto result = framework_->onInstall(engine->getConfig());
    if (result) {
        installed_ = true;
    }
    return result;
}

bool ModuleFrame::uninstall() {
    if (!installed_) {
        return true;
    }
    auto result = framework_->onUninstall();
    if (result) {
        installed_ = false;
    }
    // Never invoking
    for (auto cb : httpServerList_) {
        kratos::corelib::dispose(cb);
    }
    framework_ = nullptr;
    httpServerList_.clear();
    ProtocolRegisterSingleton::destroy();
    EntityManagerSingleton::destroy();
    EntitySearchSingleton::destroy();
    ObjectPoolManagerSinglton::destroy();
    return result;
}

bool ModuleFrame::start(Engine * engine) {
    if (started_) {
        return true;
    }
    engine_ = engine;
    if (!ProtocolRegisterRef.doRegister()) {
        fatallog << ProtocolRegisterRef.getError() << endlog;
        return false;
    }
    engine->getProtocolAnalyzer()->setAnalyzer("4L:4");
    auto result = framework_->onStart(engine->getConfig());
    if (result) {
        started_ = true;
    }
    return result;
}

bool ModuleFrame::stop() {
    if (stopped_) {
        return true;
    }
    if (!started_) {
        return true;
    }
    if (framework_->onStop()) {
        stopped_ = true;
        started_ = false;
        return true;
    }
    return false;
}

std::uint32_t ModuleFrame::getType() {
    return static_cast<std::underlying_type<ModuleType>::type>(framework_->onModuleType());
}

void ModuleFrame::onTick(std::time_t millionseconds) {
    disposeAllHttpDeadCb();
    EntityManagerRef.update();
    framework_->onTick(millionseconds);
    for (auto timerID : cancelledTimers_) {
        auto it = timerMap_.find(timerID);
        if (it != timerMap_.end()) {
            timerMap_.erase(it);
        }
    }
    if (!cancelledTimers_.empty()) {
        cancelledTimers_.clear();
    }
}

std::string ModuleFrame::execute(const std::string & command) {
    return framework_->onCommand(command);
}

void ModuleFrame::onPathJoin(PathID pathID, SiteType siteType, UserData userData) {
    framework_->onPathJoin(pathID, siteType, userData);
}

void ModuleFrame::onPathDisjoin(PathID pathID, SiteType siteType) {
    framework_->onPathDisjoin(pathID, siteType);
    for (auto& typeIt : typePathMap_) {
        auto& paths = typeIt.second;
        for (auto pathIt = paths.begin(); pathIt != paths.end(); pathIt++) {
            if (*pathIt == pathID) {
                paths.erase(pathIt);
                return;
            }
        }
    }
}

void ModuleFrame::onFindPathTimeout(SiteType siteType, UserData userData) {
    framework_->onFindPathTimeout(siteType, userData);
}

void ModuleFrame::onPathData(PathID pathID, SiteType siteType, const char * data, std::size_t size) {
    ProtocolHeader header(data);
    FRAMEWORK_CHECK(header.length > kratos::NETWORK_IOLOOP_RECV_BUFFER_MAX_SIZE,
        "Invalid path packet size, MSGID:" << header.msgID << ", MSG SIZE:" << header.length);
    if ((header.msgID > getMsgRealID(LocatorMsgID::FIRST_MSG_ID)) &&
        (header.msgID < getMsgRealID(LocatorMsgID::LAST_MSG_ID))) {
        if ((header.msgID == getMsgRealID(LocatorMsgID::RPC_REQ_PATH) ||
            header.msgID == getMsgRealID(LocatorMsgID::RPC_ACK_PATH))) {
            RPCRef.onRPCPathMessage(pathID, header.msgID, data, size);
        } else if ((header.msgID == getMsgRealID(LocatorMsgID::RPC_REQ_ENTITY)) ||
            (header.msgID == getMsgRealID(LocatorMsgID::RPC_ACK_ENTITY))) {
            RPCRef.onRPCEntityMessage(pathID, header.msgID, data, size);
        } else {
            doLocatorMessage(pathID, header.msgID, data, size);
        }
    } else {
        auto msg = ProtocolRegisterRef.getMessage(header.msgID);
        if (!msg) {
            framework_->onPathData(pathID, siteType, header.msgID, data, size);
            return;
        }
        msg->Clear();
        FRAMEWORK_CHECK(!msg->ParseFromArray(data + sizeof(ProtocolHeader), size - sizeof(ProtocolHeader)),
            "ParseFromArray() failed, Message ID invalid:" << header.msgID);
        framework_->onPathData(pathID, siteType, header.msgID, *msg);
    }
}

void ModuleFrame::onLocatorChange(std::uint64_t entityID, std::uint32_t entityType, PathID, PathID) {
    framework_->unregisterEntity(entityID);
    framework_->registerEntity(entityID, entityType);
}

void ModuleFrame::onSessionJoin(SessionID sessionID) {
    framework_->onSessionJoin(sessionID);
}

void ModuleFrame::onSessionDisjoin(SessionID sessionID) {
    framework_->onSessionDisjoin(sessionID);
}

void ModuleFrame::onSessionData(SessionID sessionID, const char * data, std::size_t size) {
    ProtocolHeader header(data);
    FRAMEWORK_CHECK(header.length > kratos::NETWORK_IOLOOP_RECV_BUFFER_MAX_SIZE,
        "Invalid session packet size, MSGID:" << header.msgID << ", MSG SIZE:" << header.length);
    auto msg = ProtocolRegisterRef.getMessage(header.msgID);
    if (!msg) {
        framework_->onSessionData(sessionID, header.msgID, data, size);
        return;
    }
    msg->Clear();
    FRAMEWORK_CHECK(!msg->ParseFromArray(data + sizeof(ProtocolHeader), size - sizeof(ProtocolHeader)),
        "ParseFromArray() failed, Message ID invalid:" << header.msgID);
    framework_->onSessionData(sessionID, header.msgID, *msg);
}

void ModuleFrame::onTimer(TimerHandle timerID) {
    auto it = timerMap_.find(timerID);
    if (it == timerMap_.end()) {
        return;
    }
    if (it->second.cb) {
        it->second.cb(timerID, it->second.userData);
    } else {
        framework_->onTimer(timerID, it->second.userData);
    }
    if (it->second.once) {
        timerMap_.erase(it);
    }
}

void ModuleFrame::onSiteJoin(const kratos::grid::SiteID & siteID) {
    framework_->onSiteJoin(siteID);
}

void ModuleFrame::onSiteDisjoin(const kratos::grid::SiteID & siteID) {
    framework_->onSiteDisjoin(siteID);
}

void ModuleFrame::onSiteData(const kratos::grid::SiteID & siteID, const char * data, std::size_t size) {
    ProtocolHeader header(data);
    FRAMEWORK_CHECK(header.length > kratos::NETWORK_IOLOOP_RECV_BUFFER_MAX_SIZE,
        "Invalid site packet size, MSGID:" << header.msgID << ", MSG SIZE:" << header.length);
    if ((header.msgID >= getMsgRealID(LocatorMsgID::RPC_REQ_SITE) &&
        header.msgID <= getMsgRealID(LocatorMsgID::RPC_ACK_SITE))) {
        RPCRef.onRPCSiteMessage(siteID, header.msgID, data, size);
        return;
    }
    auto msg = ProtocolRegisterRef.getMessage(header.msgID);
    if (!msg) {
        framework_->onSiteData(siteID, header.msgID, data, size);
        return;
    }
    msg->Clear();
    FRAMEWORK_CHECK(!msg->ParseFromArray(data + sizeof(ProtocolHeader), size - sizeof(ProtocolHeader)),
        "ParseFromArray() failed, Message ID invalid:" << header.msgID);
    framework_->onSiteData(siteID, header.msgID, *msg);
}

void ModuleFrame::onLocatorJoin(PathID) {
    // entity cache research
    EntitySearchCacheManagerRef.research();
}

void ModuleFrame::onLocatorDisjoin(PathID pathID) {
    // TODO
    framework_->onLocatorRehash(pathID);
}

bool ModuleFrame::isStarted() {
    return started_;
}

void ModuleFrame::setServiceName(const std::string& serviceName) {
    serviceName_ = serviceName;
}

void ModuleFrame::onHttpData(SessionID sessionID, const kratos::util::HttpProto& httpProto) {
    framework_->onHttpData(sessionID, httpProto);
}

void ModuleFrame::onSessionConnected(SessionID sessionID, std::uint64_t userData) {
    framework_->onSessionConnected(sessionID, userData);
}
